package com.netease.nimlib.fts.query;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;

public class QueryHit {
	
	public static class HitInfo {
		public HitInfo(int start, int end) {
			this.start = start;
			this.end = end;
		}

		public int start;
		public int end;
		
		@Override
		public String toString() {
			return "[" + start + "," + end + "]";
		}
		
		static class HitInfoComparator implements Comparator<HitInfo> {
			public int compare(HitInfo h1, HitInfo h2) {
				return h1.start - h2.start;
			}
		}
	}

	public ArrayList<HitInfo> hit(String result, String query) {
		result = result.toLowerCase(Locale.CHINA);
		query = query.toLowerCase(Locale.CHINA);
		ArrayList<HitInfo> hits = new ArrayList<>();
		ArrayList<String> queryTokens = Tokenizer.getInstance().getTokens(
				query, false);
		for (String token : queryTokens) {
			int start = -1; // reset for each token
			int tokenType = Tokenizer.getInstance().getCharType(token.charAt(0));
			while((start = result.indexOf(token, ++start))!=-1 ) {
				if(tokenType !=2 && start > 0) {
					// 如果是中文，不需要前缀匹配，如果是英文符号要前缀匹配
					int prefixType = Tokenizer.getInstance().getCharType(result.charAt(start - 1));
					if(tokenType == prefixType) {
						continue;
					}
				}
				
				hits.add(new HitInfo(start, start + token.length() - 1)); // prefix match
			}
		}
		
		return mergeHitInfos(hits); // merge hits
	}
	
	protected ArrayList<HitInfo> mergeHitInfos(HitInfo... hits) {
		ArrayList<HitInfo> hs = new ArrayList<>(hits.length);
		java.util.Collections.addAll(hs, hits);
		return mergeHitInfos(hs);
	}
	
	protected ArrayList<HitInfo> mergeHitInfos(ArrayList<HitInfo> hitInfos) {
		if (hitInfos == null) {
			return null;
		}

		// 1. merge intersection
		ArrayList<HitInfo> mergeResult = new ArrayList<>(hitInfos.size());
		for (HitInfo h : hitInfos) {
			boolean merge = false;
			for (HitInfo m : mergeResult) {
				if (hasIntersection(h, m)) {
					m.start = Math.min(h.start, m.start);
					m.end = Math.max(h.end, m.end);
					merge = true;
					break;
				}
			}

			if (!merge) {
				mergeResult.add(h);
			}
		}

		// 2. sort
		Collections.sort(mergeResult, new HitInfo.HitInfoComparator());

		// 3. merge continuous range
		ArrayList<HitInfo> result = new ArrayList<>(mergeResult.size());
		if (mergeResult.size() <= 1) {
			result.addAll(mergeResult);
		} else {
			HitInfo h = mergeResult.get(0);
			HitInfo p;
			for (int i = 1; i < mergeResult.size(); i++) {
				p = mergeResult.get(i);
				if (h.end + 1 == p.start) {
					// continuous, expand range
					h.end = p.end;
				} else {
					// commit
					result.add(new HitInfo(h.start, h.end));

					// start next
					h.start = p.start;
					h.end = p.end;
				}

				if (i == mergeResult.size() - 1) {
					// reach the end, commit last
					result.add(new HitInfo(h.start, h.end));
				}
			}
		}

		return result;
	}

	private boolean hasIntersection(HitInfo h1, HitInfo h2) {
		HitInfo l = h1.start <= h2.start ? h1 : h2;
		HitInfo r = l == h1 ? h2: h1;
		return l.end >= r.start;
	}
	
	/**
	 * ***************** Single instance *****************
	 */
	public static QueryHit getInstance() {
		return InstanceHolder.instance;
	}

	public static class InstanceHolder {
		public final static QueryHit instance = new QueryHit();
	}
}
